package com.csaba.swing.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.csaba.connector.model.Session;
import com.csaba.swing.gui.ContextListener;
import com.csaba.swing.gui.GUIContext;

/**
 * This class implements a special cache object, that contains values related to 
 * session objects. On closing the session, the cache drops content related to 
 * the session.
 * 
 * @author Gabe
 */
public abstract class SessionCache<E> implements ContextListener
{

	private final Map<Session, E[]> sessionMap = new HashMap<Session, E[]>();

	public SessionCache()
	{
		GUIContext.getInstance().addContextListener(this);
	}

	public E[] getAll( final E[] result )
	{
		final List<E> all = new ArrayList<E>();

		final List<Session> sessions = GUIContext.getInstance().getSessions();

		for ( final Session session : sessions )
		{
			E[] elems = null;
			synchronized ( sessionMap )
			{
				elems = sessionMap.get(session);
			}
			if ( elems == null )
			{
				elems = loadAndStore(session);
			}

			if ( elems == null )
				continue;

			for ( int i = 0; i < elems.length; i++ )
			{
				all.add(elems[i]);
			}
		}

		return all.toArray(result);
	}

	public E[] get( final Session session )
	{
		E[] elems = null;
		synchronized ( sessionMap )
		{
			elems = sessionMap.get(session);
		}
		if ( elems == null )
		{
			elems = loadAndStore(session);
		}
		return elems;
	}

	/**
	 * Finds sessions based on the item cached. 
	 * @param item
	 * @return null if the item is not found in the cache, otherwise list of all sessions containing the item.
	 */
	public Session[] getSession( final E item )
	{
		if ( item == null )
		{
			return null;
		}
		final List<Session> matches = new LinkedList<Session>();
		synchronized ( sessionMap )
		{
			for ( final Session session : sessionMap.keySet() )
			{
				final E[] items = sessionMap.get(session);
				for ( int i = 0; i < items.length; i++ )
				{
					if ( item.equals(items[i]) )
					{
						matches.add(session);
						break;
					}
				}
			}
		}

		if ( matches.size() == 0 )
			return null;

		return matches.toArray(new Session[matches.size()]);
	}

	/**
	 * Checks whether the item is cached in the cache. If the item is null
	 * false is returned.
	 * @param item
	 * @return true if the item is in the cache. 
	 */
	public boolean contains( final E item )
	{
		if ( item == null )
			return false;

		synchronized ( sessionMap )
		{
			for ( final E[] items : sessionMap.values() )
			{
				for ( int i = 0; i < items.length; i++ )
				{
					if ( item.equals(items[i]) )
						return true;
				}
			}
		}
		return false;
	}

	@Override
	public void contextEventPerformed( final int eventType )
	{
		if ( eventType == SESSION_REMOVED )
		{
			final List<Session> activeSessions = GUIContext.getInstance().getSessions();
			synchronized ( sessionMap )
			{
				final Set<Session> keys = sessionMap.keySet();
				for ( final Iterator<Session> iterator = keys.iterator(); iterator.hasNext(); )
				{
					final Session session = iterator.next();
					if ( !activeSessions.contains(session) )
						iterator.remove();
				}
			}
		}

	}

	public void clear()
	{
		synchronized ( sessionMap )
		{
			sessionMap.clear();
		}
	}

	public E[] reloadAll( final E[] result )
	{
		clear();
		return getAll(result);
	}

	private E[] loadAndStore( final Session session )
	{
		final E[] elems = load(session);
		if ( elems != null )
			synchronized ( sessionMap )
			{
				sessionMap.put(session, elems);
			}
		return elems;
	}

	/**
	 * In this method the data loading should be implemented. This method 
	 * is invoked by the "get" methods of the cache if necessary.
	 * @param session
	 * @return
	 */
	protected abstract E[] load( Session session );
}
